import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.HashMap;

/**
 * Implementation of the RemoteHashTable interface.
 * 
 * @author James Marquardt
 * @author Matt Adams
 * @author Justin Wong
 */
public class RemoteNodeRegistryService implements RemoteNodeRegistry {

	/**
	 * The m value of this system.
	 */
	private int m_value;
	
	/**
	 * Mappings of node_id to location.
	 */
	private HashMap<Integer, HostPort> node_mappings;
	
	/**
	 * The host and port of the last node that registered. This is used as an arbitrary node 
	 * reference for newly joined nodes.
	 */
	private HostPort lastHostPortRegistered = null;
	
	/**
	 * Main method for RemoteHashTableService
	 * @param args command line arguments
	 */
	public static void main(String[] args) {
		if(args.length != 3) {
    		Log.err("Usage: java RemoteHashTableService <port> <registry-name> <m value>");
    		System.exit(0);
    	}
    	
    	try {
            RemoteNodeRegistryService obj = new RemoteNodeRegistryService(Integer.parseInt(args[2]));
            RemoteNodeRegistry stub = 
            		(RemoteNodeRegistry) UnicastRemoteObject.exportObject(obj, 0);

            // Bind the remote object's stub in the registry
            LocateRegistry.createRegistry(Integer.parseInt(args[0]));
            
            Registry registry = LocateRegistry.getRegistry(Integer.parseInt(args[0]));
            registry.bind(args[1], stub);

            Log.out("Exported HashTable service as " + args[1] + " on registry port" +
            		" " + args[0]);
        } catch (Exception e) {
            Log.err("Could not create registry: " + e);
            System.exit(0);
        }

	}
	
	/**
	 * Constructor for RemoteHashTableService
	 * 
	 * @param the_m_value The m value for the system.
	 */
	public RemoteNodeRegistryService(int the_m_value) {
		m_value = the_m_value;
		node_mappings = new HashMap<Integer, HostPort>();
		
	}
	
	@Override
	public RegistrationPacket registerNode(final HostPort the_hostPort) throws RemoteException {
		if (node_mappings.size() == (int) Math.pow(2, m_value)) {
			throw new IllegalArgumentException("Creating too many nodes for the chord. m value: " + m_value + " number of nodes:" + node_mappings.size());
		} else {
			int id = the_hostPort.hashCode()%m_value;
			int i = 1;
			while(node_mappings.containsKey(id)) {
				id = (id + i * i) % (int) Math.pow(2, this.m_value);
				i++;
			}
			node_mappings.put(id, the_hostPort);
			if (node_mappings.size() > 1) {
			    this.lastHostPortRegistered = the_hostPort;
			}		
			
			return new RegistrationPacket(this.lastHostPortRegistered, this.m_value, id);
		}
	}
	
    @Override
    public int getMValue() {
        return this.m_value;
    }
}
